Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.

...powered by www.netzwerkartist.de...

 << zurück
Visual C# 2005 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2005

Visual C# 2005
1.320 S., mit 2 CDs, 59,90 Euro
Galileo Computing
ISBN 3-89842-586-X
gp Kapitel 4 Das Klassendesign (Teil 1)
  gp 4.1 Einführung in die Objektorientierung
    gp 4.1.1 Das objektorientierte Paradigma
    gp 4.1.2 Vorteile der objektorientierten Programmierung
    gp 4.1.3 Zusammenfassung
  gp 4.2 Die Klassendefinition
    gp 4.2.1 Die Deklaration von Objektvariablen
    gp 4.2.2 Zugriffsmodifizierer einer Klasse
    gp 4.2.3 Der Projekttyp »Klassenbibliothek«
    gp 4.2.4 Splitten einer Klassendefinition mit »partial«
    gp 4.2.5 Zusammenfassung
  gp 4.3 Objektmethoden
    gp 4.3.1 Der Aufruf einer Methode
    gp 4.3.2 Methoden mit Parameterliste
    gp 4.3.3 Rückgabewert einer Methode
    gp 4.3.4 Variablen in einer Methode
    gp 4.3.5 Zugriffsmodifizierer einer Methode
    gp 4.3.6 Besondere Aspekte einer Parameterliste
    gp 4.3.7 Referenz- und Wertparameter
    gp 4.3.8 Methodenüberladung
    gp 4.3.9 Aufruf überladener Methoden mit impliziter Konvertierung
    gp 4.3.10 Zusammenfassung
  gp 4.4 Objekteigenschaften
    gp 4.4.1 Datenkapselung mit Eigenschaftsmethoden sicherstellen
    gp 4.4.2 Die Ergänzung der Klasse »Circle«
    gp 4.4.3 Lese- und schreibgeschützte Eigenschaften
    gp 4.4.4 Sichtbarkeit der Accessoren »get« und »set«
    gp 4.4.5 Konstanten
    gp 4.4.6 Methode oder Eigenschaft?
    gp 4.4.7 Die Trennung von Daten und Code
    gp 4.4.8 Der Zugriff auf private Daten
    gp 4.4.9 Zusammenfassung
  gp 4.5 Konstruktoren
    gp 4.5.1 Die Konstruktoren in der Klasse »Circle«
    gp 4.5.2 Die Konstruktoraufrufe
    gp 4.5.3 Definition von Konstruktoren
    gp 4.5.4 »internal«-Konstruktoren
    gp 4.5.5 »private«-Konstruktoren
    gp 4.5.6 Konstruktorverkettung
    gp 4.5.7 Zusammenfassung
  gp 4.6 Der Destruktor
    gp 4.6.1 Das Zerstören von Objekten
    gp 4.6.2 Der Garbage Collector
    gp 4.6.3 Die Bereitstellung eines Destruktors
    gp 4.6.4 Das Zerstören eines Objekts
    gp 4.6.5 Die »Dispose«-Methode
    gp 4.6.6 Der Garbage Collector in Aktion
    gp 4.6.7 Zusammenfassung
  gp 4.7 Arbeiten mit Objektreferenzen
    gp 4.7.1 Prüfen auf Initialisierung
    gp 4.7.2 Die Deklaration vom Typ »Object«
    gp 4.7.3 Mehrere Referenzen auf ein Objekt
    gp 4.7.4 Den Typ einer Objektreferenz mit »ToString« ermitteln
    gp 4.7.5 Der »is«-Operator zur Typfeststellung
    gp 4.7.6 Referenzvergleiche
    gp 4.7.7 Das Klonen von Objekten
    gp 4.7.8 Zusammenfassung


Galileo Computing

4.7 Arbeiten mit Objektreferenzedowntop

Der Zustand einer Objektvariablen, die nicht initialisiert ist, wird durch null beschrieben. Verwechseln Sie diesen Zustand nicht mit der Zahl »0«. null gibt an, dass eine Variable nicht initialisiert ist, also kein konkretes Objekt referenziert.


Galileo Computing

4.7.1 Prüfen auf Initialisierung  downtop

Das wäre beispielsweise nach der Deklaration mit


Circle meinKreis;

der Fall. Auf eine Objektreferenz kann nur zugegriffen werden, wenn sie auf ein konkretes Objekt verweist. Ist man sich über den Zustand der Variablen im Unklaren, muss dieser, wie im folgenden Codefragment gezeigt, überprüft werden:


if(meinKreis == null)
  // die Variable obj referenziert kein konkretes Objekt
else
  // obj ist eine gültige Objektreferenz

Ebenso gut können Sie auch mit


if(meinKreis != null) ...

prüfen, ob meinKreis ein gültiger Verweis ist. Liefert diese Bedingung true, kann eine Methode des Objekts aufgerufen oder eine Eigenschaft ausgewertet werden, ohne einen Laufzeitfehler zu riskieren.

Sehen wir uns nun noch an, was passiert, wenn wir eine Objektvariable deklarieren und anschließend ohne vorhergehende Initialisierung auf null testen.


class TestClass {
  static void AnyMethod() {
    Circle meinKreis;
    if(meinKreis == null)  {
      // die Variable meinKreis referenziert kein Objekt
      Console.WriteLine("Das Objekt existiert nicht!");
      meinKreis = new Circle();
    }
    else
      // meinKreis ist eine gültige Objektreferenz
      Console.WriteLine("Das Objekt existiert");
    // weitere Anweisungen
    Console.ReadLine();
  }
}

Der C#-Compiler ist intelligent genug zu erkennen, dass der Objektreferenz meinKreis vor der Prüfung im if-Statement zu keinem Zeitpunkt eine gültige Referenz zugewiesen wurde, und bricht die Kompilierung mit einer Fehlermeldung ab. Akzeptiert wird die Prüfung nur, wenn zwischen der Deklaration und der Bedingungsprüfung meinKreis zu irgendeinem Zeitpunkt ein gültiges Objekt referenziert.


Galileo Computing

4.7.2 Die Deklaration vom Typ »Object«  downtop

Möglicherweise ist der Typ einer Objektvariablen zur Entwicklungszeit noch nicht bekannt und kann erst zur Laufzeit einer Anwendung bestimmt werden. Das befreit natürlich nicht von der Notwendigkeit einer Typangabe. Sie können die Objektvariable in diesem Fall vom Typ Object deklarieren, beispielsweise mit


object myObj;

Der Typ Object bzw. sein C#-Alias object nimmt eine ganz besondere Stellung in der .NET-Klassenbibliothek ein. Alle .NET-Klassen siedeln sich mehr oder weniger tief im Dickicht einer baumartigen Vererbungshierarchie an. Das gilt nicht nur für die Klassen, die in der Klassenbibliothek veröffentlicht werden, das gilt gleichermaßen auch für alle benutzerdefinierten Klassen. So wie sich jeder Ast eines Baumes auf einen Stamm zurückverfolgen lässt, lässt sich auch ausnahmslos jede Klasse auf den Typ Object zurückführen, denn grundsätzlich jede .NET-Klasse wird aus Object abgeleitet – selbst dann, wenn dies nicht explizit angegeben ist.

Erinnern Sie sich an die Erläuterung des Begriffs der Vererbung am Anfang dieses Kapitels? Es wurde eine Klasse Luftfahrzeug als Basisklasse angenommen, von der sich die Klassen Starrflügler, Hubschrauber und Zeppelin ableiteten mit dem Ziel, durch die Vererbung Merkmale des allgemeinen Typs Luftfahrzeug an die abgeleiteten Klassen zu vererben. Daraus folgte auch die Erkenntnis, dass ein Starrflügler ein Luftfahrzeug ist, ebenso wie ein Hubschrauber oder ein Zeppelin.


Das Objekt einer abgeleiteten Klasse ist gleichzeitig auch immer ein Objekt der Basisklasse.

Diese Feststellung können Sie nun abstrakt portieren. Da Object die Basisklasse aller .NET-Klassen ist, müssen alle abgeleiteten Typen gleichzeitig auch immer vom Typ Object sein. Aus diesem Grund kann die Variable myObj vom Typ Object zur Laufzeit auf jeden beliebigen .NET-Typ verweisen.

Betrachten Sie das folgende Codefragment. Die Variable meinKreis ist spezifisch deklariert und vom Typ der Klasse Circle, die Variable obj allgemein gehalten und vom Typ Object. Zu einem späteren Zeitpunkt könnte der Variablen obj die Referenz auf das Objekt vom Typ Circle zugewiesen werden:


object obj;
Circle meinKreis = new Circle();
obj = meinKreis;

Selbstverständlich wäre auch das Zuweisen einer Referenz vom Typ ClassA oder ClassB an obj möglich.

Im ersten Moment mag es verlockend klingen, alle Objektvariablen vom Typ object zu deklarieren. Diese scheinbare Flexibilität sollte aber dennoch – falls es nicht unumgänglich ist – vermieden werden, da die typgenaue Deklaration einige Vorteile hat:

gp  Der Code exakt spezifizierter Objektvariablen wird schneller ausgeführt, da der Typ bereits bei der Kompilierung feststeht.
gp  In der Intellisense-Liste werden alle sichtbaren Member des Typs angeboten.
gp  Syntaxfehler, beispielsweise durch einen fehlerhaften Methodenaufruf, werden von der Entwicklungsumgebung sofort erkannt.
gp  Die Wahrscheinlichkeit, dass unvorhersehbare Fehler auftreten, die während der Codierung noch nicht erkannt werden, wird minimiert.

Galileo Computing

4.7.3 Mehrere Referenzen auf ein Objekt  downtop

Es kommt immer wieder vor, dass mehrere Referenzen auf dasselbe Objekt zeigen. Betrachten Sie dazu das folgende Codefragment:


Circle meinKreis = new Circle();
Circle secondObj = meinKreis;

Zuerst wird eine Variable vom Typ Circle deklariert und initialisiert. Anschließend wird der Verweis auf das Circle-Objekt der Variablen secondObj zugewiesen. Trotz zwei verschiedener Objektreferenzen liegt nur ein konkretes Objekt vor, das allerdings über beide Referenzen angesprochen werden kann.

Weisen Sie einem Feld des Objekts über eine der beiden Referenzen einen Wert zu, beispielsweise mit


meinKreis.Radius = 10

können Sie auch mit der zweiten Objektreferenz den Inhalt der Eigenschaft auswerten:


Console.WriteLine(secondObj.Radius)

An der Konsole wird 10 angezeigt, da die Variable secondObj auf dasselbe Objekt verweist wie die Variable meinKreis. Wird ein Objekt mehrfach referenziert, spielt es demnach keine Rolle, über welche Referenz der Eigenschaft ein Wert zugewiesen bzw. ein Feld ausgelesen wird – die Operation wird auf demselben konkreten Objekt ausgeführt.

Eine Mehrfachreferenzierung hat auch noch weitere Konsequenzen: Geben Sie eine der Referenzen mit null frei, wird das Objekt nicht zerstört, denn die zweite, weiterhin gültige Referenz garantiert das Weiterleben des Objekts.


Circle meinKreis1 = new Circle();
Circle meinKreis2;
meinKreis2 = meinKreis1;
meinKreis2.Radius = 20;
meinKreis2 = null;
Console.WriteLine(meinKreis1.Radius);

An der Konsole wird in diesem Fall immer noch der Inhalt der Eigenschaft Radius ausgegeben. Erst mit der Freigabe der letzten gültigen Referenz auf eine Klasseninstanz wird das Objekt tatsächlich unwiederbringlich freigegeben.


Galileo Computing

4.7.4 Den Typ einer Objektreferenz mit »ToString« ermitteldowntop

Unter Umständen ist es zur Laufzeit von Interesse, den Typ eines Objekts zu erfahren. Jede .NET-Klasse beerbt die Klasse Object, sowohl die in der Klassenbibliothek vordefinierten als auch die benutzerdefinierten Klassen. Durch die Implementierungsvererbung veröffentlicht jede Klasse die von Object bereitgestellten Methoden. Eine dieser Methoden, die als Ergebnis des Aufrufs standardmäßig den voll qualifizierenden Namen der Klasse liefert, lautet ToString. Ein voll qualifizierender Name setzt sich aus der Angabe des Namespaces, dem die Klasse verwaltungstechnisch zugeordnet ist, und dem Klassennamen zusammen.

Nehmen wir an, die Klasse Circle sei im Namespace CircleApplication definiert. Der Aufruf von ToString mit


Circle meinKreis = new Circle();
Console.WriteLine(meinKreis.ToString());

würde dann zu folgender Ausgabe an der Konsole führen:


CircleApplication.Circle

Allerdings dürfen Sie nicht unbedingt erwarten, grundsätzlich immer ein Ergebnis, bestehend aus der Angabe des Namespace und des Typs, zu erhalten. Viele Klasse überschreiben die Methode ToString, d.  h., in der Klasse wird die Standardimplementierung an typspezifische Anforderungen angepasst.


Galileo Computing

4.7.5 Der »is«-Operator zur Typfeststellung  downtop

Nehmen wir an, eine Variable wäre wie folgt deklariert:


object myObj;

Da wir es hier mit einem allgemein gültigen Typ zu tun haben, kann man davon ausgehen, dass sich erst während der Ausführung des Programms der tatsächliche Typ der Variablen myObj aus den Laufzeitbedingungen heraus ergeben wird. Nehmen wir weiter an, es würde sich dabei um den Typ ClassA oder ClassB handeln. Also wird myObj zu einem späteren Zeit entweder mit


myObj = new ClassA();

oder mit


myObj = new ClassB();

initialisiert. Beide Klassen zeichnen sich durch individuelle Methoden aus. Beispielsweise könnte in der Klasse ClassA die Methode Show definiert sein, in der Klasse ClassB die Methode Hide. Sie können sich sicherlich vorstellen, dass je nach Typ, der von myObj beschrieben wird, entweder die Show- oder die Hide-Methode aufgerufen werden muss. Die Frage, die sich stellt, lautet daher: Wie kann ich den Typ, der sich hinter einer Objektreferenz verbirgt, feststellen?

C# bietet uns zur Beantwortung den is-Operator an, der den Laufzeittyp eines Objekts mit einem angegebenen Typ überprüft. Die Syntax des Operators lautet folgendermaßen:


// Syntax des is-Operators
Ausdruck is Typ

Der Vergleich zwischen Ausdruck und Typ liefert true, wenn der Ausdruck, also die Objektvariable, in den rechts von is stehenden Typ umgewandelt werden kann. Damit ist dieser Operator dazu prädestiniert, in einer if-Anweisung eingesetzt zu werden:


if(myObj is ClassA)
  Console.WriteLine("Typ: ClassA");
else
  Console.WriteLine("Typ: ClassB");

Einer Einschränkung unterliegt der is-Operator allerdings. Sehen wir uns dazu das folgende Codefragment an:


object myObj;
myObj = new ClassA();
myObj = null;
if(myObj is ClassA)
  Console.WriteLine("Typ: ClassA");
else
  Console.WriteLine("Typ: ClassB");

Die Variable myObj ist vom Typ object deklariert. Ihr wird in der zweiten Codezeile die Referenz auf ein Objekt vom Typ ClassA übergeben. Anschließend geben wir die Referenz durch die Zuweisung von null wieder frei. Es stellt sich nun die Frage, wie das System zur Laufzeit damit umgeht. Erstaunlicherweise lautet die Konsolenausgabe:


Typ: ClassB

Ist der zu untersuchende Ausdruck null, lautet der Rückgabewert nämlich false, aber da wir mit dem else-Zweig alle anderen Bedingungen gleichermaßen behandeln, kommt es zu einer falschen Annahme. Im weiteren Verlauf eines Programms könnte das zu einem Laufzeitfehler und zum unplanmäßigen Beenden des Programms führen, wenn hinter dem else-Zweig nicht nur eine einfache Konsolenausgabe, sondern der Aufruf einer Methode des ClassB-Objekts programmiert ist. Wenn man sich hinsichtlich des Zustands einer Objektvariablen nicht sicher ist, sollte dieser daher zuerst geprüft werden:


if(myObj != null) 
{
  if(myObj is ClassA)
    Console.WriteLine("Typ: ClassA");
  else
    Console.WriteLine("Typ: ClassB");
}
else
  Console.WriteLine("Objektreferenz ungültig.");

Wir sollten uns die folgende wichtige Regel merken:


Wird mit dem is-Operator eine Typüberprüfung auf eine Referenz vorgenommen, die null ist, lautet der Rückgabewert false.


Galileo Computing

4.7.6 Referenzvergleiche  downtop

Wird über die Gleichheit von Objekten gesprochen, gilt es, eine ganz wichtige Frage zu klären: Sind zwei Objekte als »gleich« anzusehen, wenn sie dieselben Eigenschaftswerte aufweisen, oder gelten Objekte dann als »gleich«, wenn zwei Referenzen auf dasselbe konkrete Objekt verweisen? An einem anschaulichen Beispiel hinterfragt: Sind die beiden Objekte obj1 und obj2 vom Typ der Klasse Circle des folgenden Codefragments gleich?


class MyClass {
  static void Main(string[] args) {
    Circle obj1 = new Circle();
    Circle obj2 = new Circle();
    obj1.Radius = 22;
    obj2.Radius = 22;
  }
}

Offensichtlich handelt es sich hierbei um zwei verschiedene Objekte, die jeweils über eine eigene Referenz angesprochen werden. Es ist festzuhalten, dass die Eigenschaft Radius in beiden Objekten denselben Inhalt hat, nämlich 22. Gelten nun die beiden Objekte als »gleich«?

In der Programmierung werden die Begrifflichkeiten genau unterschieden: Zwei Objektreferenzen gelten als identisch, wenn sie auf zwei verschiedene Objekte verweisen, deren Zustand – also die Werte der Eigenschaften – gleich ist, während man von referenzieller Gleichheit spricht, wenn zwei Referenzen auf dieselbe Speicheradresse zeigen. Demnach beschreiben die beiden Objektvariablen obj1 und obj2 identische, jedoch nicht gleiche Objekte.

Die beiden folgenden Codezeilen zeigen den Fall referenzieller Gleichheit.


Circle obj1 = new Circle();
Circle obj2 = obj1;

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 4.12   Gleichheit von Objektreferenzen

Um festzustellen, ob zwei Objektreferenzen auf dasselbe konkrete Objekt zeigen, bieten sich drei Möglichkeiten an:

gp  Die statische Methode ReferenceEquals der Klasse Object.
gp  Die Methode Equals. Da diese Methode in der Klasse Object definiert ist und ausnahmslos jede Klasse der .NET-Klassenbibliothek aus dieser abgeleitet wird, kann Equals auf jeden beliebigen Typ aufgerufen werden.
gp  Der Vergleichsoperator »==«
a
Achtung   An dieser Stelle ist bereits eine Begriffsklärung notwendig: Was ist eine statische Methode? In den meisten Fällen wird eine Klasse definiert, um daraus ein konkretes Objekt zu erzeugen. Der Zugriff auf die Eigenschaften und Methoden setzt dann in jedem Fall eine Objektreferenz voraus. Beabsichtigen Sie allerdings, eine allgemein gültige Funktionssammlung bereitzustellen, ist es nicht unbedingt notwendig, die Elementfunktionen auf ein konkretes Objekt aufzurufen. Denken Sie beispielsweise an die beiden Methoden WriteLine und ReadLine der Klasse Console. Sie rufen diese Methoden auf, ohne dass Sie vorher eine Instanz der Klasse Console angefordert haben. Methoden, die nicht von der Existenz eines konkreten Objekts abhängen, werden im objektorientierten Sprachgebrauch als statische Methoden bezeichnet. Kennzeichnend für den Aufruf statischer Methoden ist die Voranstellung des Klassennamens anstelle einer Objektreferenz, beispielsweise: Console.WriteLine(...); Math.Cos(...);

Alle drei Möglichkeiten – ReferenceEquals, Equals und »==« – sind zunächst grundsätzlich nur auf Referenztypen anzuwenden, unterscheiden sich untereinander jedoch in wichtigen Details.

Ohne Einschränkungen ist die Methode ReferenceEquals einsetzbar, die beim Aufruf zwei Objektreferenzen entgegennimmt und einen booleschen Wert als Ergebnis des Aufrufs liefert:


bool bol = Object.ReferenceEquals(obj1, obj2);

Die Methode ist statisch, wird daher auf den Typnamen aufgerufen und liefert true, wenn beide Verweise dasselbe Objekt im Speicher referenzieren. Andernfalls ist der Rückgabewert false.

Im folgenden Codebeispiel wird der Einsatz dieser Methode demonstriert. Dazu werden insgesamt drei Referenzen des Typs Circle deklariert: obj1, obj2 und obj3. obj1 und obj3 verweisen auf dieselbe Speicheradresse, referenzieren also dasselbe konkrete Objekt. Auf ein zweites Objekt wird über obj2 verwiesen, das – um dem Sachverhalt noch ein wenig mehr Würze zu geben – zustandsgleich mit dem ersten Objekt ist.


// --------------------------------------------------------------
// Beispiel: ...\Kapitel 4\ReferenceEquals
// --------------------------------------------------------------
class Program {
  static void Main(string[] args) {
    Circle obj1 = new Circle();
    Circle obj2 = new Circle();
    // der Referenz obj3 wird die Referenz auf das Objekt obj1 zugewiesen
    Circle obj3 = obj1;
    // der Zustand der Objekte obj1 und obj2 ist identisch
    obj1.Radius = 4711;
    obj2.Radius = 4711;
    // die Referenzen obj1 und obj2 vergleichen
    Console.Write("Die Objekte obj1 und obj2 sind ");
    bool bol = Object.ReferenceEquals(obj1, obj2);
    if(bol)
      Console.WriteLine("gleich");
    else
      Console.WriteLine("ungleich");
    // die Referenzen obj1 und obj3 vergleichen
    Console.Write("Die Objekte obj1 und obj3 sind "); 
    bol = Object.ReferenceEquals(obj1, obj3);
    if(bol)
      Console.WriteLine("gleich");
    else
      Console.WriteLine("ungleich");
    Console.ReadLine();
  }
}
class Circle {
  public int Radius = 0;
}

Erwartungsgemäß wird der erste Aufruf zu der Feststellung führen, dass zwei verschiedene Speicheradressen von obj1 und obj2 beschrieben werden, während der zweite Aufruf die referenzielle Gleichheit zwischen obj1 und obj3 feststellt.


Die Objekte obj1 und obj2 sind ungleich
Die Objekte obj1 und obj3 sind gleich

Wir ändern nun den Code und rufen anstelle der statischen Methode ReferenceEquals die Methode Equals eines Objekts auf. Equals ist zwar in der Klasse Circle nicht explizit definiert, wird aber aus der Klasse Object geerbt, die bekanntermaßen die Basisklasse jeder .NET-Klasse ist.


// --------------------------------------------------------------
// Beispiel: ...\Kapitel 4\Equals
// --------------------------------------------------------------
class Program {
  static void Main(string[] args) {
    ...
    // die Referenzen obj1 und obj2 vergleichen
    Console.Write("Die Objekte obj1 und obj2 sind ");
    if(obj1.Equals(obj2))
      Console.WriteLine("gleich");
    else
      Console.WriteLine("ungleich");
    // die Referenzen obj1 und obj3 vergleichen
    Console.Write("Die Objekte obj1 und obj3 sind "); 
    if(obj1.Equals(obj3))
      Console.WriteLine("gleich");
    else
      Console.WriteLine("ungleich");
    Console.ReadLine();
  }
}
...

Das an der Konsole angezeigte Ergebnis ist natürlich dasselbe wie beim Aufruf der Methode ReferenceEquals.

Von der Methode Equals gibt es sowohl eine statische als auch eine objektgebundene Definition. Im Codefragment oben wurde die letztere mit


obj1.Equals(obj2);

aufgerufen. Gleichwertig können Sie auch die statische Definition mit folgender Anweisung benutzen:


Object.Equals(obj1, obj2);

Beiden Methodenaufrufen liefern true an den Aufrufer zurück, wenn die Objektvariablen auf dasselbe Objekt zeigen.

Bisher haben Sie nur gesehen, dass das Ergebnis sowohl des Aufrufs der Methode ReferenceEquals als auch der Methode Equals gleich ist. Nun stellt sich natürlich sofort die Frage, warum in der Klasse Object zwei anscheinend doch funktionell identische Methoden definiert sind.

Die Antwort darauf ist sehr einfach und hängt mit der Vererbung und den damit verbundenen Möglichkeiten einer Klassendefinition zusammen. Die ursprüngliche Funktionalität der Methode Equals kann von einer abgeleiteten Klasse überschrieben werden. Eine Methode zu überschreiben bedeutet, in einer abgeleiteten Klasse eine gleichnamige Methode zu definieren, die jedoch eine andere Codeimplementierung aufweist. Überschrieben wird eine geerbte Methode, um diese beispielsweise an die individuellen Anforderungen einer Klasse anzupassen. Equals kann von jeder Klasse überschrieben werden, und somit haben Sie auch keine Garantie, dass die Methode immer so funktioniert, wie Sie es oben gesehen haben. Im Zweifelsfall müssen Sie die Dokumentation der entsprechenden Klasse zur Hand nehmen. Das erinnert uns an die weiter oben beschriebene Methode ToString, die ebenfalls typspezifisch überschrieben werden darf.

Wenden wir uns nun noch abschließend kurz dem Vergleichsoperator »==« zu, der nichts anderes darstellt als eine zusätzliche sprachliche Spielart von C#.


if(obj1 == obj2)
  Console.WriteLine("gleich");
else
  Console.WriteLine("ungleich");

Das Ergebnis des Prozeduraufrufs unterscheidet sich nicht von dem der Methode ReferenceEquals bzw. der nicht überschriebenen Methode Equals.

Der Sonderfall: Vergleich von Zeichenfolgen

Es gibt einen Sonderfall, der aus dem Rahmen unserer bisherigen Ausnahmen fällt und deshalb auch einer gesonderten Betrachtung unterzogen werden muss: Zeichenfolgen vom Datentyp string. Sehen Sie sich dazu das folgende Beispiel an:


// --------------------------------------------------------------
// Beispiel: ...\Kapitel 4\Stringvergleich
// --------------------------------------------------------------
class Program {
  static void Main(string[] args) {
    string str1 = "C# ist spitze!";
    string str2 = "C# ist spitze!";
    if (str1 == str2)
      Console.WriteLine("Beide Strings sind gleich");
    else
      Console.WriteLine("Die Strings sind ungleich");
    Console.ReadLine();
  }
}

Es sind zwei Objektvariablen vom Typ string deklariert, die denselben Inhalt haben. Man könnte im ersten Moment erwarten, dass in den else-Zweig der bedingten Anweisung gesprungen wird. Dem ist aber nicht so, denn tatsächlich wird an der Konsole


Beide Strings sind gleich

ausgegeben. Wie kann dieses Phänomen erklärt werden?

String-Variablen unterscheiden sich von den Variablen anderer Typen: Der Inhalt kann, wenn er einmal festgelegt ist, nicht verändert werden. Die folgenden beiden Codezeilen sollen das erklären:


string str = "Hallo Welt";
str = "Hallo Welt und guten Morgen";

In der ersten Zeile wird die Variable str deklariert und initialisiert, in der zweiten Zeile wird der Variablen ein neuer Wert zugewiesen. Tatsächlich wird aber der alte Inhalt nicht durch einen anderen ersetzt, sondern eine neue Speicheradresse reserviert, der Zeiger von der alten Adresse mit dem Inhalt »Hallo Welt« auf die neue Adresse umgebogen und der neue Inhalt »Hallo Welt und guten Morgen« hineingeschrieben.

Definitiv arbeitet dieser Code also tatsächlich mit zwei unterschiedlichen Speicheradressen – der Anwender bemerkt davon jedoch nichts. Dieses Verhalten scheint nicht sehr effizient zu sein, denn das Verbiegen eines Zeigers kostet zweifelsfrei Systemleistung. Trotzdem gibt es einen Vorteil, denn unveränderliche Zeichenfolgen sind gleichzeitig auch für die gemeinsame Nutzung eingerichtet. Das ist im obigen Beispiel Stringvvergleich der Fall und wird durch das Ausgabeergebnis an der Konsole auch bewiesen: str1 und str2 verweisen auf dieselbe Speicheradresse, weil sie denselben Inhalt beschreiben.


Galileo Computing

4.7.7 Das Klonen von Objekten  downtop

Manchmal ist es erforderlich, ein zusätzliches Objekt als exaktes Abbild eines bereits existierenden zu erstellen. Dabei soll der Anfangszustand der Kopie exakt dem Zustand des zu kopierenden Objekts entsprechen.

Die Klasse Object bietet zu diesem Zweck die Methode MemberwiseClone an, die als Rückgabewert die Referenz auf das neue Objekt liefert. Diese Methode ist wie folgt definiert:


protected Object MemberwiseClone(); 

Der Zugriffsmodifizierer protected wird erst im Rahmen der Vererbung Bedeutung erlangen. Er ähnelt sehr dem Modifizierer private. Solchermaßen deklarierte Member werden nicht veröffentlicht und können daher nicht von außerhalb der Klasse aufgerufen werden. Der Zugriff ist nur aus der aktuellen Klasse heraus oder aus einer abgeleiteten möglich. Dazu später mehr.

MemberwiseClone ist eine Methode, die jede .NET-Klasse von Object erbt. Da die Methode nicht statisch definiert ist, wird sie auf einem konkreten Objekt aufgerufen – im folgenden Codefragment als Anweisung in der Methode GetClone.


class Circle {
  public int Radius = 0;
  public Circle GetClone() {
    return (Circle)this.MemberwiseClone();
  }
}

MemberwiseClone ist vom Typ Object und muss zum Schluss in den Typ Circle konvertiert werden.

Nun wollen wir noch ein Beispielprogramm entwickeln, um uns vom Klonen eines Objekts zu überzeugen.


// --------------------------------------------------------------
// Beispiel: ...\Kapitel 4\ObjectClonen
// --------------------------------------------------------------
class Program {
  static void Main(string[] args) {
    Circle obj = new Circle();
    obj.Radius = 85;
    // das Objekt obj clonen
    Circle clonObj = obj.GetClone();
    // Referenzvergleich
    if (obj == clonObj)
      Console.WriteLine("Identische Objekte");
    else
      Console.WriteLine("Verschiedene Objekte");
    Console.Write("TestProp der Clone = {0}", clonObj.Radius);
    Console.ReadLine();
  }

An der Konsole wird nach der Feststellung der Ungleichheit der beiden Referenzen der Inhalt der Eigenschaft Radius des geklonten Objekts ausgegeben: Tatsächlich sind die Inhalte gleich.

Die Grenzen des Klonens

Eine Schwierigkeit verbirgt sich aber dennoch hinter dem Klonen: Beim Kopiervorgang kann nur bitweise kopiert werden. Hinsichtlich der Felder, die durch Werttypen beschrieben werden (z.B. int oder float), funktioniert das tadellos. Wenn ein Feld jedoch ein Referenztyp ist, wie im folgenden Code das Feld Turbine der Klasse Flugzeug, wird der Inhalt dieses Feldes nur bitweise kopiert.


class Flugzeug {
  public int Spannweite;
  public Triebwerk Turbine = new Triebwerk();
}

Das bitweise Kopieren eines auf einem Referenztyp basierenden Feldes hat eine wichtige Konsequenz: Da das Feld Turbine die Startadresse des referenzierten Objekts enthält, wird diese 1:1 in das geklonte Objekt kopiert. Die Schlussfolgerung daraus kann nur lauten, dass die vom Ursprungsobjekt referenzierten Objekte nicht geklont werden, denn sowohl das Feld des Originals als auch das der Klone verweisen auf dasselbe Objekt.

Diesen Sachverhalt wollen wir durch ein Beispiel verdeutlichen.


// --------------------------------------------------------------
// Beispiel: ...\Kapitel 4\ObjektClonen2
// --------------------------------------------------------------
class Program {
  static void Main(string[] args) {
    ClassA obj = new ClassA();
    obj.TestProp = 85;
    // das Objekt obj clonen
    ClassA clonObj = obj.GetClone();
    // Referenzvergleich
    if (obj == clonObj)
      Console.WriteLine("Identische Objekte");
    else
      Console.WriteLine("Verschiedene Objekte");
    // Referenzvergleich der Felder
    if(obj.MyOwnObj == clonObj.MyOwnObj)
      Console.WriteLine("Gleiche Feldreferenzen");
    else
      Console.WriteLine("Verschiedene Feldreferenzen");
    Console.ReadLine();
  }
}
// ----------- Klasse ClassA -----------
class ClassA {
  public int TestProp = 0;
  public ClassB MyOwnObj = new ClassB();
  // objekteigene Methode
  public ClassA GetClone() {
    return (ClassA)this.MemberwiseClone();
  }
}
// ----------- Klasse ClassB -----------
class ClassB {
  public int TestPropB = 0;
}

Entscheidend in der Definition der Klasse ClassA ist das neu hinzugekommene Feld MyOwnObj vom Typ ClassB. Mit der Instanziierung der Klasse ClassA in der Main-Prozedur wird automatisch von dieser Klasse auch ein Objekt des Typs ClassB erzeugt und die Referenz dem objekteigenen Feld MyOwnObj zugewiesen. Diese Referenz wird durch die Methode MemberwiseClone bitweise kopiert, was die Ausgabe


Gleiche Feldreferenzen

an der Konsole beweist.

a
Achtung   Ist es erforderlich, auch die von einer Klasse referenzierten Objekte zu klonen, sollte die Klasse um die Implementierung der Schnittstelle ICloneable und deren Methode Clone erweitert werden. Das Thema der Schnittstellenimplementierung führt aber an dieser Stelle deutlich zu weit und wird in Kapitel 6 noch einmal aufgegriffen.


Galileo Computing

4.7.8 Zusammenfassung  toptop

gp  Um den Typ eines Objekts festzustellen, bietet sich die Methode ToString der Klasse Object an. ToString liefert standardmäßig den voll qualifizierenden Namen der Klasse, kann aber von einer abgeleiteten Klasse überschrieben werden.
gp  Mit dem objektspezifischen Vergleichsoperator is lässt sich der Typ einer Klasse in einer bedingten Anweisung überprüfen. Werttypen sind von dieser Überprüfung ausgeschlossen.
gp  Die Klasse Object bietet mit ihrer Methode ReferenceEquals die Möglichkeit festzustellen, ob zwei Referenzen identisch sind, also auf dasselbe Objekt im Speicher verweisen.
gp  Ein Referenzvergleich kann auch mit der Methode Equals, ebenfalls ein Mitglied der Klasse Object, vorgenommen werden. Allerdings überschreiben viele Klassen diese Methode, was zu unvorhersehbaren Ergebnissen führen könnte.
gp  Eine dritte Variante des Referenzvergleichs bietet der Operator »==«, der auf zwei Operanden vom Typ einer Referenz arbeitet.
gp  Für Variablen vom Typ string gelten besondere Bedingungen. Daher ist der Referenzvergleich von Zeichenfolgen ungeeignet.
gp  Mit der Methode MemberwiseClone lässt sich eine exakte Kopie von Objekten erzeugen. Intern referenzierte dritte Objekte werden dabei nicht geklont.
 << zurück
  
  Zum Katalog
Zum Katalog: Visual C# 2005
Visual C# 2005
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Fortgeschrittene Programmierung mit Visual C# 2005






 Fortgeschrittene
 Programmierung
 mit Visual C# 2005


Zum Katalog: Einstieg in Visual C# 2005






 Einstieg in
 Visual C# 2005


Zum Katalog: Einstieg in Visual Basic 2005






 Einstieg in
 Visual Basic 2005


Zum Katalog: Visual Basic 2005






 Visual Basic 2005


Zum Katalog: Java ist auch eine Insel






 Java ist auch eine
 Insel


Zum Katalog: Konzepte und Lösungen für Microsoft-Netzwerke






 Konzepte und
 Lösungen für
 Microsoft-Netzwerke


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo








Copyright © Galileo Press 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de